SciCloj logo
This is part of the Scicloj Clojure Data Scrapbook.

Seattle Parks & Neighborhoods

Choosing where to live depends on many factors such as job opportunities and cost of living. I like walking, so one factor that is important to me is access to parks. In this analysis we’ll rank neighborhoods by park area proportional to total area. This article demonstrates how to prepare the geospatial data, calculate the value we want, and how to explore the meaning behind the numbers.

(ns index
  (:require [geo
             [geohash :as geohash]
             [jts :as jts]
             [spatial :as spatial]
             [io :as geoio]
             [crs :as crs]]
            [tech.v3.datatype.functional :as fun]
            [tablecloth.api :as tc]
            [scicloj.kindly.v4.kind :as kind]
            [scicloj.kindly.v4.api :as kindly]
            [hiccup.core :as hiccup]
            [charred.api :as charred])
  (:import (org.locationtech.jts.index.strtree STRtree)
           (org.locationtech.jts.geom Geometry Point Polygon Coordinate)
           (org.locationtech.jts.geom.prep PreparedGeometry
                                           PreparedLineString
                                           PreparedPolygon
                                           PreparedGeometryFactory)
           (java.util TreeMap)))

Gathering geospatial data

Both the neighborhood geometry and park geometry can be downloaded from Seattle GeoData:

I’ve saved a snapshot in the data directory.

The data format is gzipped GeoJSON. Java has a built-in class for handling gzip streams, and we’ll use the factual/geojson library to parse the string representation.

(defn slurp-gzip
  "Read a gzipped file into a string"
  [path]
  (with-open [in (java.util.zip.GZIPInputStream. (clojure.java.io/input-stream path))]
    (slurp in)))

Now we can conveniently load the data files we downloaded previously.

(defonce neighborhoods-geojson
  (slurp-gzip "data/Seattle/Neighborhood_Map_Atlas_Neighborhoods.geojson.gz"))
(def neighborhoods-features
  (geoio/read-geojson neighborhoods-geojson))

Let’s check that we got some data.

(count neighborhoods-features)
94

This seems like a reasonable number of neighborhoods. Each member of the dataset is called a Feature. Here is one:

(-> neighborhoods-features
    first
    kind/pprint)
{:properties
 {:OBJECTID 27,
  :L_HOOD "Ballard",
  :S_HOOD "Loyal Heights",
  :S_HOOD_ALT_NAMES nil,
  :Shape__Area 2.13206555455933E7,
  :Shape__Length 18831.00959637},
 :geometry
 #object[org.locationtech.jts.geom.Polygon 0x62f8f0a5 "POLYGON ((-122.376336564723 47.6759176989664, -122.376707907517 47.6759982290459, -122.377903057631 47.6759978849021, -122.379988625303 47.6759893429385, -122.38182788443 47.675992079457, -122.383602752477 47.675979810424, -122.385435721425 47.6759587956618, -122.387087434206 47.6759521343769, -122.387702391216 47.6759476875929, -122.388955759168 47.6759424488678, -122.390449346263 47.6759259868868, -122.391773109273 47.6759158131504, -122.392827172066 47.6759153143353, -122.392956550545 47.6759141859541, -122.392956303464 47.6763287099834, -122.392959618409 47.676929558447, -122.392961263359 47.6775085790216, -122.392971502986 47.6781034200237, -122.392975009807 47.6792517755972, -122.392983771129 47.6798142464259, -122.39300195772 47.6811303942788, -122.393014530791 47.6824285808909, -122.393016158048 47.6837341155494, -122.393022827056 47.685021586284, -122.393030577589 47.6863450742964, -122.393032746621 47.687668638629, -122.393046187318 47.6889956475812, -122.393010665051 47.6902980913372, -122.393002415356 47.6905813205566, -122.392432373275 47.6905826938111, -122.390347910813 47.6905631204492, -122.388192979124 47.6905805029145, -122.385832831602 47.6905766041982, -122.383275316007 47.690593164597, -122.383201215024 47.6905936585447, -122.381453020656 47.6905978829044, -122.379876313408 47.6905988541749, -122.379103841332 47.6906041929746, -122.377117910432 47.6906029639028, -122.376810344766 47.6906051591463, -122.376810264834 47.6905341242701, -122.376810989127 47.6900117143006, -122.376803067488 47.688976146724, -122.376794751014 47.6879443550321, -122.376800402808 47.6870068457338, -122.376784403749 47.6860923366759, -122.376785260609 47.6851813698577, -122.376780535885 47.6842704786057, -122.376787310842 47.6833707854178, -122.376777423242 47.6822860591882, -122.376767820487 47.6811938310584, -122.376770442503 47.680154307228, -122.376770065639 47.6792017979916, -122.376769882216 47.6780678404081, -122.376759750881 47.6775387646826, -122.376761985459 47.676862137338, -122.376772171763 47.6764361697665, -122.376772451106 47.6764114022001, -122.376770700102 47.6763866189683, -122.37676692258 47.6763619488292, -122.376761120573 47.6763374777451, -122.376753802121 47.6763131984825, -122.376744969247 47.6762891966531, -122.376733608705 47.6762655291531, -122.376721246367 47.6762423037704, -122.376706358898 47.6762194979699, -122.376690473449 47.6761972626929, -122.376672575492 47.6761756116819, -122.376653693551 47.6761550023704, -122.376620811506 47.6761243856628, -122.37659791092 47.6761051588887, -122.376573006221 47.6760868163511, -122.376547112434 47.6760693435982, -122.376519722058 47.6760527482053, -122.376336564723 47.6759176989664))"]}

Each feature, in our case, represents a geographic region with a geometry and some properties.

And similarly for the parks:

(defonce parks-geojson
  (slurp-gzip "data/Seattle/Park_Boundary_(details).geojson.gz"))
(def parks-features
  (geoio/read-geojson parks-geojson))
(count parks-features)
2809

There are more parks than neighborhoods, which sounds right.

(delay
  (-> parks-features
      first
      kind/pprint))
{:properties
 {:SE_ANNO_CAD_DATA "",
  :NAMEFLAG 9,
  :ADDRESS " ",
  :MAINT "DPR",
  :LEASE "N",
  :PMA_NAME "East Duwamish GS: S Chicago St",
  :SUBPARCEL 9851,
  :PMA 442,
  :REVIEW_DATE "2004-04-08T00:00:00Z",
  :GlobalID "{D4B7025E-1135-4232-88AB-CBF72932E6AE}",
  :OBJECTID 207882,
  :AMWOID "PROPERTY-EDUWSC",
  :SDQL "QL-D1",
  :USE_ nil,
  :PIN "4006000485",
  :GIS_EDT_DT "2024-01-19T14:07:23Z",
  :SHAPE_Area 1.4691943312522366E-6,
  :SHAPE_Length 0.005215887160444538,
  :GIS_CRT_DT "2024-01-19T14:07:23Z",
  :NAME "EAST DUWAMISH GREENBELT",
  :OWNER "DPR",
  :ACQ_DATE "1997-10-21T00:00:00Z"},
 :geometry
 #object[org.locationtech.jts.geom.MultiPolygon 0x34f6e6a6 "MULTIPOLYGON (((-122.28235899499998 47.525165833000074, -122.28168484899999 47.525164389000054, -122.28168307199996 47.52516438600003, -122.28145050399996 47.52516388600003, -122.281450582 47.525147437000044, -122.28092621899998 47.525146310000025, -122.28092721899998 47.52433074500004, -122.28129411599997 47.52433142700005, -122.28247185299995 47.52433360800006, -122.28270306299999 47.52433403400005, -122.28270162899997 47.52516656600005, -122.28235899499998 47.525165833000074)))"]}

And the parks are defined as geographic regions.

Drawing a map

Seattle coordinates

(def Seattle-center
  [47.608013 -122.335167])

The map we will create is A choropleth, though for now, we will use a fixed color, which is not so informative.

We will enrich every feature (e.g., neighborhood) with data relevant for its visual representation.

(defn enrich-feature [{:as   feature :keys [geometry]}
                      {:keys [tooltip-keys
                              style]}]
  (-> feature
      (update :properties
              (fn [properties]
                (-> properties
                    (assoc :tooltip (-> properties
                                        (select-keys tooltip-keys)
                                        (->> (map (fn [[k v]]
                                                    [:p [:b k] ":  " v]))
                                             (into [:div]))
                                        hiccup/html)
                           :style style))))))
(def neighborhoods-enriched-features
  (-> neighborhoods-geojson
      (charred/read-json {:key-fn keyword})
      :features
      (->> (mapv (fn [feature]
                   (-> feature
                       (enrich-feature
                        {:tooltip-keys [:L_HOOD]
                         :style {:opacity     0.3
                                 :fillOpacity 0.1
                                 :color      "purple"
                                 :fillColor  "purple"}})))))))

We will need a tile layer for our visual map:

(def openstreetmap-tile-layer
  {:url         "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
   :max-zoom    19
   :attribution "&copy;; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>"})

Here is how we may generate a Choroplet map in Leaflet:

(defn choropleth-map [details]
  (delay
    (kind/reagent
     ['(fn [{:keys [tile-layer
                    center
                    enriched-features]}]
         [:div
          {:style {:height "900px"}
           :ref   (fn [el]
                    (let [m (-> js/L
                                (.map el)
                                (.setView (clj->js center)
                                          11))]
                      (let [{:keys [url max-zoom attribution]}
                            tile-layer]
                        (-> js/L
                            (.tileLayer url
                                        (clj->js
                                         {:maxZoom     max-zoom
                                          :attribution attribution}))
                            (.addTo m)))
                      (-> js/L
                          (.geoJson (clj->js enriched-features)
                                    (clj->js {:style (fn [feature]
                                                       (-> feature
                                                           .-properties
                                                           .-style))}))
                          (.bindTooltip (fn [layer]
                                          (-> layer
                                              .-feature
                                              .-properties
                                              .-tooltip)))
                          (.addTo m))
                      #_(->> enriched-features
                             (run! (fn [{:keys [properties geometry]}]
                                     (let [{:keys [style tooltip]} properties]
                                       (case (:type geometry)
                                         "Polygon" (-> js/L
                                                       (.polygon (-> geometry
                                                                     :coordinates
                                                                     first
                                                                     clj->js)
                                                                 (-> style
                                                                     (or {})
                                                                     clj->js (or style {})))
                                                       (.bindTooltip tooltip)
                                                       (.addTo m))
                                         "MultiPolygon" (-> js/L
                                                            (.multiPolygon (-> geometry
                                                                               :coordinates
                                                                               clj->js)
                                                                           (-> style
                                                                               (or {})
                                                                               clj->js (or style {})))
                                                            (.bindTooltip tooltip)
                                                            (.addTo m))
                                         ;; else
                                         (-> geometry
                                             :type
                                             (str " - unrecognized geometry type")
                                             js/alert))))))))}])
      details]
     {:reagent/deps [:leaflet]})))

For our basic neighborhoods map:

(delay
  (choropleth-map
   {:tile-layer openstreetmap-tile-layer
    :center     Seattle-center
    :enriched-features neighborhoods-enriched-features}))

Now, let us see the parks:

(def parks-enriched-features
  (-> parks-geojson
      (charred/read-json {:key-fn keyword})
      :features
      (->> (mapv (fn [feature]
                   (-> feature
                       (enrich-feature
                        {:tooltip-keys [:PMA_NAME :NAME]
                         :style {:opacity     0.3
                                 :fillOpacity 0.1
                                 :color      "darkgreen"
                                 :fillColor  "darkgreen"}})))))))
(delay
  (choropleth-map
   {:tile-layer openstreetmap-tile-layer
    :center     Seattle-center
    :enriched-features parks-enriched-features}))

Coordinate conversions

Both datasets use the WGS84 coordinate system.

EPSG:4326

For area computations we need to convert them to a coordinate system which is locally correct in terms of distances in a region around Seattle.

EPSG:2285

United States (USA) - Oregon and Washington. [1 3]:

Center coordinates Projected bounds WGS84 bounds
[1692592.39 -541752.55] [[559165.71 -1834123.81] [2832684.98 777497.1]] [[-124.79 41.98] [-116.47 49.05]]
(def crs-transform
  (geo.crs/create-transform (geo.crs/create-crs 4326)
                            (geo.crs/create-crs 2285)))
(defn wgs84->Seattle
  "Transforming latitude-longitude coordinates
  to local Euclidean coordinates around Seattle."
  [geometry]
  (geo.jts/transform-geom geometry crs-transform))

Some geometrical functions

(defn area [geometry]
  (.getArea geometry))
(defn buffer [geometry radius]
  (.buffer geometry radius))
(defn intersection [geometry1 geometry2]
  (.intersection geometry1 geometry2))

Geometry datasets

(defn geojson->dataset [geojson dataset-name]
  (-> geojson
      (->> (map (fn [{:keys [geometry properties]}]
                  (assoc properties :geometry geometry))))
      tc/dataset
      (tc/map-columns :geometry [:geometry] wgs84->Seattle)
      (tc/set-dataset-name dataset-name)))
(def neighborhoods
  (-> neighborhoods-features
      (geojson->dataset "Seattle neighborhoods")))
(delay
  (-> neighborhoods
      (tc/drop-columns [:geometry])
      kind/table))
OBJECTID L_HOOD S_HOOD S_HOOD_ALT_NAMES Shape__Area Shape__Length

27

Ballard

Loyal Heights

2.13206555455933E7

18831.00959637

28

Ballard

Ballard

Adams

2.25521329700012E7

29926.3393002841

29

Ballard

Whittier Heights

1.41956874919434E7

15934.4392663199

30

Ballard

West Woodland

2.21993668730164E7

21789.6510873493

31

North Central

Phinney Ridge

Woodland Park

3.21231178777466E7

27120.7859379681

32

North Central

Wallingford

Meridian, Tangle Town, Northlake

4.20683426137695E7

33040.2469861198

33

North Central

Fremont

2.65037565718689E7

25999.2849357187

34

North Central

Green Lake

3.73146297898865E7

25387.7074962486

35

Northeast

View Ridge

2.56129799440918E7

22657.0394732799

36

Northeast

Ravenna

University Village

3.67833915366516E7

31163.6208763331

37

Northeast

Sand Point

1.92498571397095E7

21396.6958008648

38

Northeast

Bryant

1.54850780333252E7

20928.0017243062

39

Northeast

Windermere

Hawthorne Hills

2.23639666230164E7

33332.8291968304

40

Northeast

Laurelhurst

1.90517768183594E7

26489.2422321737

41

Northeast

Roosevelt

Fairview

2.1491594324707E7

22866.0797374364

42

University District

University of Washington

2.55236751180115E7

30983.2126641678

43

Queen Anne

East Queen Anne

1.93546995965576E7

21488.397378299

44

Queen Anne

West Queen Anne

1.79749859528503E7

20631.7057292923

45

Queen Anne

Lower Queen Anne

Uptown, Seattle Center

1.75156244566956E7

20496.7289222394

46

Queen Anne

North Queen Anne

2.9604288333252E7

26753.5542817429

47

Cascade

Westlake

3645184.07894897

11017.3231739615

48

Cascade

Eastlake

7426907.96139526

16672.5117837403

49

Cascade

South Lake Union

1.6107105419342E7

29323.5462843847

50

Magnolia

Lawton Park

5.5091525290863E7

38766.0673349107

51

Magnolia

Briarcliff

Carleton Park

2.65509365782471E7

26609.7912261135

52

Magnolia

Southeast Magnolia

1.84692638882751E7

20223.1801501522

53

Central Area

Madrona

1.38454828085938E7

17052.9427067693

54

Central Area

Harrison/Denny-Blaine

1.36033956731873E7

17241.2743191595

55

Central Area

Minor

Central District, Squire Park

1.7933144708252E7

18241.5487859812

56

Central Area

Leschi

1.80917413764954E7

20413.0560516882

57

Central Area

Mann

Central District, Garfield

1.11338288620605E7

17843.105758736

58

Central Area

Atlantic

Judkins Park, Jackson Place, Coleman

2.06649862726135E7

25426.2311626675

59

Downtown

Pike-Market

Pike Place Market

2343359.32580566

7326.58173024624

60

Downtown

Belltown

Denny Regrade

9845864.78594971

17836.7271544608

61

Downtown

International District

Chinatown, ID, Little Saigon

6169375.81655884

11772.3871410021

62

Downtown

Central Business District

Commercial Core, West Edge

9477398.61834717

18209.4628624588

63

Downtown

First Hill

9517568.47363281

13225.2335159542

64

Downtown

Yesler Terrace

5348937.24771118

9611.35354641661

65

Downtown

Pioneer Square

7545979.0869751

17297.5867148564

66

Interbay

Interbay

3.35039410921631E7

64207.5867611718

67

Greater Duwamish

SODO

4.96031675905914E7

38520.9885110863

68

Greater Duwamish

Georgetown

5.13050726461182E7

45963.6656769607

69

Greater Duwamish

South Park

2.94234521522369E7

30433.4048686311

70

Greater Duwamish

Harbor Island

1.78323007354431E7

24381.2966280601

71

West Seattle

Seaview

1.8463061797821E7

23122.2640032568

72

West Seattle

Gatewood

Morgan Junction

2.28217074689484E7

23801.2647005122

73

West Seattle

Arbor Heights

2.61973140573883E7

28162.4351386166

74

West Seattle

Alki

2.45834691841736E7

45568.1378536906

75

West Seattle

North Admiral

Belvidere

4.85814163202972E7

30687.7844700837

76

West Seattle

Fairmount Park

1.61539000976257E7

24955.7128414259

77

West Seattle

Genesee

Alaska Junction, West Seattle Junction

2.11649765813446E7

19870.8696089241

78

West Seattle

Fauntleroy

Endolyne, Arroyo Heights, Brace Point

3.38818638706665E7

42276.5317877774

79

Beacon Hill

North Beacon Hill

Jefferson Park

4.84759144557953E7

33865.4891739901

80

Beacon Hill

Mid-Beacon Hill

5.26204707883301E7

40522.1829275323

81

Beacon Hill

South Beacon Hill

3.84488268989563E7

38996.7151070849

82

Beacon Hill

Holly Park

New Holly

8444235.97859192

15848.3624185113

83

Rainier Valley

Brighton

1.8366938275177E7

18911.3293490837

84

Rainier Valley

Dunlap

1.96016012150879E7

20847.6531922997

85

Rainier Valley

Rainier Beach

3.31282361414948E7

34965.6132100475

86

Rainier Valley

Rainier View

Lake Ridge

1.82485661794434E7

34791.8218147105

87

Rainier Valley

Mount Baker

North Rainier

2.89869952343292E7

26318.9819054871

88

Rainier Valley

Columbia City

Columbia Heights

3.74065764927979E7

35177.0608039675

89

Delridge

Highland Park

3.98103334158325E7

31111.0630837714

90

Delridge

North Delridge

Avalon, Luna Park, Pigeon Point

2.94546565672607E7

23549.9313666304

91

Delridge

Riverview

3.30382671929626E7

25917.1176391015

92

Delridge

High Point

2.33430083368683E7

23180.3963596714

93

Delridge

South Delridge

White Center

1.79233162238464E7

23416.3405359794

94

Delridge

Roxhill

Westwood Village

1.87810598441162E7

22360.0472289145

95

Seward Park

Seward Park

Lakewood, Hillman City

4.38352790059662E7

52212.7868103531

96

Northeast

Wedgwood

3.05997136261597E7

23840.2187274324

97

Capitol Hill

Portage Bay

Roanoke Park

4421635.26721191

10013.2739563989

98

Capitol Hill

Montlake

North Capitol Hill

2.1474786741394E7

46165.83098345

99

Capitol Hill

Madison Park

Broadmoor, Washington Park

2.46809502223511E7

26230.4347480002

100

Capitol Hill

Broadway

Pike/Pine

3.02392405212402E7

26851.9474418256

101

Capitol Hill

Stevens

Interlaken Park, Miller Park, Madison-Miller

2.60395920790405E7

25522.3456368768

102

Lake City

Victory Heights

2.15127314890137E7

20753.0018992847

103

Lake City

Matthews Beach

3.09809783014832E7

31369.8925404597

104

Lake City

Meadowbrook

1.70348487789307E7

18770.2820324215

105

Lake City

Olympic Hills

2.58367421641541E7

19993.8160570763

106

Lake City

Cedar Park

2.04414858487244E7

22788.760382662

110

Northwest

Broadview

4.81134497303162E7

34640.309665739

111

Northwest

Bitter Lake

2.76903364438171E7

26446.2030540938

112

Northgate

Haller Lake

4.72030810796204E7

29413.5534484151

113

Northgate

Pinehurst

Jackson Park

2.94388508770142E7

26171.2717608103

114

Northwest

North Beach/Blue Ridge

3.10622649915771E7

32947.8059119899

115

Northgate

Licton Springs

North College Park

2.17047062611389E7

20132.8982458333

116

Northgate

Maple Leaf

Olympic View

3.43804707826233E7

25503.0574082691

117

Northwest

Crown Hill

1.58657504196777E7

18997.8080379411

118

Northwest

Greenwood

4.20445636731262E7

26501.5265911049

119

Ballard

Sunset Hill

Golden Gardens, Shilshole

2.43356204689941E7

29416.7300633571

121

University District

University District

1.36862548569946E7

21581.6459789299

123

University District

University Heights

Cowen Park

1.03757480522461E7

15163.9069096811

124

Downtown

Denny Triangle

Denny Regrade

5128172.31045532

10574.1779826691

126

Greater Duwamish

Industrial District

6.24341310114746E7

88995.7569839975

(def parks
  (-> parks-features
      (geojson->dataset "Seattle parks")
      ;; avoiding some [linestring pathologies](https://gis.stackexchange.com/questions/50399/fixing-non-noded-intersection-problem-using-postgis)
      (tc/map-columns :geometry [:geometry] #(buffer % 1))))
(delay
  (-> parks
      (tc/drop-columns [:geometry])
      (tc/head 20)
      kind/table))
SE_ANNO_CAD_DATA NAMEFLAG ADDRESS MAINT LEASE PMA_NAME SUBPARCEL PMA REVIEW_DATE GlobalID OBJECTID AMWOID SDQL USE_ PIN GIS_EDT_DT SHAPE_Area SHAPE_Length GIS_CRT_DT NAME OWNER ACQ_DATE

9

DPR

N

East Duwamish GS: S Chicago St

9851

442

2004-04-08T00:00:00Z

{D4B7025E-1135-4232-88AB-CBF72932E6AE}

207882

PROPERTY-EDUWSC

QL-D1

4006000485

2024-01-19T14:07:23Z

1.4691943312522366E-6

0.005215887160444538

2024-01-19T14:07:23Z

EAST DUWAMISH GREENBELT

DPR

1997-10-21T00:00:00Z

9

DPR

N

East Duwamish GS: S Chicago St

4779

443

2004-04-08T00:00:00Z

{26B4F4BC-F9AE-42D4-B062-51387C6D9FFF}

207883

PROPERTY-EDUWC

QL-D1

0603001220

2024-01-19T14:07:23Z

1.460570191594512E-7

0.0015461198840460832

2024-01-19T14:07:23Z

EAST DUWAMISH GREENBELT

DPR

1994-10-06T00:00:00Z

9

DPR

N

East Duwamish GS: S Chicago St

4711

443

2004-04-08T00:00:00Z

{E97D7F7F-2666-4008-A905-2F416A66C2A1}

207884

PROPERTY-EDUWC

QL-D1

0603001220

2024-01-19T14:07:23Z

9.743577447048599E-8

0.0013271653095071236

2024-01-19T14:07:23Z

EAST DUWAMISH GREENBELT

DPR

1994-10-06T00:00:00Z

9

DPR

N

East Duwamish GS: S Chicago St

4779

443

2004-04-08T00:00:00Z

{C28A4700-BB4F-4C25-928E-57022F6D001B}

207885

PROPERTY-EDUWC

QL-D1

0603001220

2024-01-19T14:07:23Z

4.87386722719263E-8

0.0011082207640129814

2024-01-19T14:07:23Z

EAST DUWAMISH GREENBELT

DPR

1994-10-06T00:00:00Z

9

DPR

N

East Duwamish GS: S Chicago St

4712

443

2004-04-08T00:00:00Z

{4DB6A932-327A-4E2E-AC8A-C068D114AA61}

207886

PROPERTY-EDUWC

QL-D1

0603001220

2024-01-19T14:07:23Z

9.757207624044397E-8

0.0013284113092379031

2024-01-19T14:07:23Z

EAST DUWAMISH GREENBELT

DPR

1994-10-06T00:00:00Z

9

DPR

N

Genesee Park and Playfield

4618

409

1899-12-30T00:00:01Z

{AD2E47C9-323B-4DDE-8186-98A4FC79C3F8}

207887

PROPERTY-GENPK

QL-D1

4154300585

2024-01-19T14:07:23Z

2.892289652506793E-6

0.007000440212562059

2024-01-19T14:07:23Z

GENESEE PARK AND PLAYFIELD

DPR

1996-06-03T00:00:00Z

9

5244 SW JACOBSEN ROAD

DPR

N

Wolf Creek Ravine Natural Area

16397

278

2004-04-19T00:00:00Z

{A7E54352-0DC3-47E4-B562-DAA0F18B78E2}

207888

PROPERTY-WLFCRAV

QL-D1

5344200125

2024-01-19T14:07:23Z

6.25557675764001E-8

0.0010504796990982068

2024-01-19T14:07:23Z

WOLF CREEK RAVINE NATURAL AREA

DPR

2004-04-07T00:00:00Z

9

DPR

N

Jefferson Park

15550

114

2004-04-08T00:00:00Z

{B82B7BD2-DCD9-49A2-BE5D-30C1D9BF4392}

207889

PROPERTY-JEFFPK

QL-D1

1624049270

2024-01-19T14:07:23Z

3.2670040661028276E-6

0.018380332716286137

2024-01-19T14:07:23Z

JEFFERSON PARK

DPR

1988-07-25T00:00:00Z

9

DPR

N

Queen Anne Boulevard

753

328

2004-04-09T00:00:00Z

{3DA31C7B-C33D-4DD9-9092-71B1DA135BC8}

207890

PROPERTY-QABL

QL-D1

2533300180

2024-01-19T14:07:23Z

5.790553187521671E-9

4.111515370853996E-4

2024-01-19T14:07:23Z

QUEEN ANNE BOULEVARD

DPR

1910-07-25T00:00:01Z

9

DPR

N

Queen Anne Boulevard

4688

328

2004-04-09T00:00:00Z

{D3DA12EE-31E0-4476-95CE-1739E8FF8550}

207891

PROPERTY-QABL

QL-D1

2533300180

2024-01-19T14:07:23Z

5.139665582905777E-9

3.9228813174494234E-4

2024-01-19T14:07:23Z

QUEEN ANNE BOULEVARD

DPR

1910-07-25T00:00:01Z

7

DPR

N

Ravenna Woods

15807

4411

2004-05-05T00:00:00Z

{B1A2F3C6-C43A-4600-8064-A3DEFAE61DDC}

207892

PROPERTY-RAVWOOD

QL-D1

0925049168

2024-01-19T14:07:23Z

5.585829095854119E-7

0.0030915009702507513

2024-01-19T14:07:23Z

RAVENNA WOODS

DPR

2002-07-02T00:00:00Z

9

DPR

N

Montlake Playfield

1507

376

1998-10-15T00:00:00Z

{BF38EE1A-DBAB-400C-B083-B3F8B6A1EB39}

207893

PROPERTY-MTLKPF

QL-D1

4089400080

2024-01-19T14:07:23Z

3.917576385699785E-7

0.0026768636765498316

2024-01-19T14:07:23Z

MONTLAKE PLAYFIELD

DPR

1933-12-31T00:00:01Z

9

DPR

N

Montlake Playfield

1507

376

2004-03-30T00:00:00Z

{4CC47610-4927-465F-A346-04F66008D480}

207894

PROPERTY-MTLKPF

QL-D1

6788202280

2024-01-19T14:07:23Z

1.1273410584671711E-6

0.004459900867682873

2024-01-19T14:07:23Z

MONTLAKE PLAYFIELD

DPR

1933-12-31T00:00:01Z

9

DPR

N

Montlake Playfield

1507

376

2004-03-30T00:00:00Z

{10310B09-822A-4344-95CC-2D5D90C2466C}

207895

PROPERTY-MTLKPF

QL-D1

6788202280

2024-01-19T14:07:23Z

1.4591960880701082E-6

0.006312257575513224

2024-01-19T14:07:23Z

MONTLAKE PLAYFIELD

DPR

1933-12-31T00:00:01Z

9

4357 PALATINE AVE N

DPR

N

Fremont Peak Park

16454

4424

2004-10-06T00:00:00Z

{A13D75B6-B355-4BDF-84CF-4D2838170201}

207896

PROPERTY-FRMPKPK

QL-D1

6610000810

2024-01-19T14:07:23Z

6.118914081332531E-8

0.0011669443524576426

2024-01-19T14:07:23Z

FREMONT PEAK PARK

DPR

2004-09-30T00:00:00Z

9

DPR

N

SW Queen Anne Greenbelt

16459

234

2005-01-06T00:00:00Z

{A13C70F6-F0AE-4E55-A12C-990BBC545F8D}

207897

PROPERTY-SWQAGRB

QL-D1

7705100037

2024-01-19T14:07:23Z

5.565195258199712E-9

3.556012201607765E-4

2024-01-19T14:07:23Z

SW QUEEN ANNE GREENBELT

DPR

2004-11-04T00:00:00Z

3

9026 4TH AVE S

DPR

N

Marra-Desimone Park

16455

4447

2007-02-21T00:00:00Z

{E641A30D-FE4B-4B1D-9358-7F0F71324691}

207898

PROPERTY-MARDESPK

QL-D1

3224049044

2024-01-19T14:07:23Z

2.211413981358282E-6

0.006388002898489655

2024-01-19T14:07:23Z

MARRA-DESIMONE PARK

DPR

2004-12-28T00:00:00Z

9

9026 4TH AVE S

DPR

N

Marra-Desimone Park

16455

4447

2007-02-21T00:00:00Z

{6A440787-F078-4DB9-9187-DA0E029A8E18}

207899

PROPERTY-MARDESPK

QL-D1

3224049035

2024-01-19T14:07:23Z

1.981420659558925E-6

0.009774073825241834

2024-01-19T14:07:23Z

MARRA-DESIMONE PARK

DPR

2004-12-28T00:00:00Z

9

DPR

Y

Meadowbrook Playfield

803

352

{49C14F14-94FF-4080-9DE8-56EDF006145A}

207900

PROPERTY-MBPF

QL-D1

2024-01-19T14:07:23Z

5.268695375108406E-7

0.00550990095454217

2024-01-19T14:07:23Z

MEADOWBROOK PLAYFIELD

SSD1

9

DPR

N

Meadowbrook Teen Life Center

352

{D099D793-CFEA-44E9-A57B-D8A1BA4525AF}

207901

PROPERTY-MBPF

QL-D1

2024-01-19T14:07:23Z

8.504523207167994E-8

0.0011865488770908574

2024-01-19T14:07:23Z

MEADOWBROOK TEEN LIFE CENTER

SSD1

A Spatial index structure

We need an index structure to quickly match between the two sets of geometries.

See the JTS SearchUsingPreparedGeometryIndex tutorial.

(defn make-spatial-index [dataset & {:keys [geometry-column]
                                     :or   {geometry-column :geometry}}]
  (let [tree (org.locationtech.jts.index.strtree.STRtree.)]
    (doseq [row (tc/rows dataset :as-maps)]
      (let [geometry (row geometry-column)]
        (.insert tree
                 (.getEnvelopeInternal geometry)
                 (assoc row
                        :prepared-geometry
                        (org.locationtech.jts.geom.prep.PreparedGeometryFactory/prepare geometry)))))
    tree))
(def parks-index
  (make-spatial-index parks))
(defn intersecting-places [region spatial-index]
  (->> (.query spatial-index (.getEnvelopeInternal region))
       (filter (fn [row]
                 (.intersects (:prepared-geometry row) region)))
       tc/dataset))

For example, let us find the parks intersecting with the first neighborhood:

(delay
  (-> neighborhoods
      :geometry
      first
      (intersecting-places parks-index)
      (tc/select-columns [:PMA_NAME :NAME])))

_unnamed [2 2]:

:PMA_NAME :NAME
Salmon Bay Park SALMON BAY PARK
Loyal Heights Playfield LOYAL HEIGHTS PLAYFIELD

A Joined dataset

We compute a spatial join of the two datasets.

Note that even though many parks will appear as intersecting many neighbourhoods, this is not too memory-heavy, since they are references to the same map.

(def neighborhoods-with-parks
  (-> neighborhoods
      (tc/map-columns :parks
                      [:geometry]
                      #(intersecting-places % parks-index))))
(delay
  (-> neighborhoods-with-parks
      (tc/map-columns :n-parks
                      [:parks]
                      tc/row-count)
      (tc/select-columns [:L_HOOD :n-parks])))

Seattle neighborhoods [94 2]:

:L_HOOD :n-parks
Ballard 2
Ballard 7
Ballard 4
Ballard 5
North Central 11
North Central 27
North Central 15
North Central 19
Northeast 15
Northeast 12
Northgate 8
Northwest 32
Northgate 6
Northgate 18
Northwest 5
Northwest 36
Ballard 9
University District 11
University District 21
Downtown 3
Greater Duwamish 5

Computing areas

For every neighborhood, we will compute the proportion of its area covered by parks.

TODO: L_HOOD should be used, S_HOOD produces too many rows to be understood (maybe S_HOOD would be interesting for looking at one L_HOOD at a time)

(delay
  (-> neighborhoods-with-parks
      (tc/map-columns :neighborhood-area
                      [:geometry]
                      area)
      (tc/map-columns :intersection-area
                      [:geometry :parks]
                      (fn [neigh-geometry parks]
                        (->> parks
                             :geometry
                             (map (fn [park-geometry]
                                    (area
                                     (.intersection (.buffer park-geometry 1)
                                                    neigh-geometry))))
                             fun/sum)))
      (tc/map-columns :park-names
                      [:parks]
                      (fn [parks]
                        (->> parks
                             :PMA_NAME
                             distinct
                             vec)))
      (tc/add-column :area-proportion
                     #(fun// (:intersection-area %)
                             (:neighborhood-area %)))
      (tc/select-columns [:L_HOOD
                          :park-names
                          :neighborhood-area
                          :intersection-area
                          :area-proportion])
      (tc/order-by [:area-proportion] :desc)))

Seattle neighborhoods [94 5]:

:L_HOOD :park-names :neighborhood-area :intersection-area :area-proportion
Northeast [Warren G. Magnuson Park] 1.92498571E+07 1.26944073E+07 0.65945463
Magnolia [Magnolia Manor Park 5.50915253E+07 2.57689116E+07 0.46774729
Lawton Park
Discovery Park
Kiwanis Memorial Preserve Park
Discovery Park Boulevard
Discovery Park Tidelands
Kiwanis Ravine Overlook
Daybreak Star
Commodore Park]
Delridge [Greg Davis Park 2.94546566E+07 1.32455699E+07 0.44969358
Cottage Grove Park
Rotary Viewpoint
Camp Long
West Seattle Golf Course
West Seattle Stadium
Longfellow Creek GS: North
Puget Boulevard
West Duwamish Greenbelt
Pigeon Point
Delridge Playfield
Westbridge Shops
WEST DUWAMISH GREENBELT]
North Central [Ravenna Boulevard 3.73146298E+07 1.53146452E+07 0.41041932
Green Lake Park
Green Lake
Crescent Place
North Park Shops]
Seward Park [Martha Washington Park 4.38352790E+07 1.32526645E+07 0.30232874
Seward Park
Lake Washington Boulevard
Lakewood Playground
Lakewood Moorage
Genesee Park and Playfield
Stan Sayres Memorial Park]
Delridge [Delridge and Myrtle 3.30382672E+07 8.76957592E+06 0.26543692
West Duwamish GS: Riverview
Riverview Playfield
West Duwamish GS: Puget Park
Puget Creek Edible Park
Puget Creek Greenspace
Puget Ridge Playground
Delridge Wetland
Puget Park
Puget Boulevard
Pigeon Point]
North Central [Cascade Place 3.21231179E+07 8.34975296E+06 0.25992972
Sunset Place
Rainier Place
Woodland Park Zoo
Woodland Park
Linden Orchard Park
Green Lake Park]
Northgate [Hubbard Homestead Park 2.94388509E+07 7.64686119E+06 0.25975407
Victory Creek Park
Pinehurst Playground
Flicker Haven Natural Area on Thornton Creek
Licorice Fern Natural Area on Thornton Creek
Jackson Park Golf Course]
Capitol Hill [Washington Park and Arboretum 2.14747867E+07 5.45298611E+06 0.25392504
Interlaken Park
Montlake Boulevard
Lake Washington Boulevard
East Montlake Park
Grand Army Cemetery
Montlake Playfield
Harvard-Miller/Roanoke Annex
West Montlake Park]
West Seattle [Fauntleroy Park 3.38818639E+07 7.39981718E+06 0.21840053
Arroyos Natural Area
Stim Bullitt Natural Area
Endolyne Park
Fauntleroy Creek Ravine
Lincoln Park
Kilbourne Park
Solstice Park]
Delridge [Longfellow Creek GS: South] 1.79233162E+07 1.32742172E+05 0.00740612
Queen Anne [Blaine Place 1.75156245E+07 1.06952952E+05 0.00610615
Counterbalance Park
Kinnear Place
Kerry Park (Franklin Place)
Kerry Park and Viewpoint
Ward Springs Park]
Northwest [Crown Hill Glen Crown Hill Park] 1.58657504E+07 9.49434049E+04 0.00598417
University District [Union Bay Boglands #2 Union Bay Boglands #1 Fritz Hedges Waterway Park] 2.55236751E+07 1.42125035E+05 0.00556836
Greater Duwamish [Georgetown Pump Station Oxbow Park Georgetown Playfield] 5.13050726E+07 2.81438227E+05 0.00548558
Lake City [Thornton Creek Addition: North Branch 2.58367422E+07 1.41195814E+05 0.00546492
Albert Davis Park
Little Brook Park]
West Seattle [West Seattle Junction 2.11649766E+07 9.18911781E+04 0.00434166
Junction Plaza Park
Ercolini Park
Dakota Place Park
48th Ave SW/SW Charlestown St
Fauntleroy Place]
West Seattle [Lowman Beach Park Morgan Junction Park] 1.84630618E+07 5.18603372E+04 0.00280887
Downtown [McGraw Square Westlake Square Urban Triangle Park] 5.12817231E+06 1.16681726E+04 0.00227531
Greater Duwamish [] 1.78323007E+07 0.00000000E+00 0.00000000
Greater Duwamish [] 4.96031676E+07 0.00000000E+00 0.00000000

TODO: The area proportions would be best represented in a bar chart to accompany the table TODO: summarizing the quartiles of values might be useful as well TODO: An interesting map would show just one L_HOOD and all the parks, perhaps choosing the “winner” with most park space and showing where in Seattle it is, and what parks are there.

Conclusion

Park access is fairly uniform and high in Seattle. TODO: can we compare it to another city?

source: projects/geography/seattle-parks/notebooks/index.clj